// \copyright
// Copyright 2009-2015, by the California Institute of Technology.
// ALL RIGHTS RESERVED.  United States Government Sponsorship
// acknowledged.

#include "ActiveTextLoggerTester.hpp"
#include <fstream>
#include "Fw/Types/StringUtils.hpp"

#define INSTANCE 0
#define MAX_HISTORY_SIZE 10
#define QUEUE_DEPTH 10

namespace Svc {

// ----------------------------------------------------------------------
// Construction and destruction
// ----------------------------------------------------------------------

ActiveTextLoggerTester ::ActiveTextLoggerTester()
    : ActiveTextLoggerGTestBase("Tester", MAX_HISTORY_SIZE), component("ActiveTextLogger") {
    this->initComponents();
    this->connectPorts();
}

ActiveTextLoggerTester ::~ActiveTextLoggerTester() {
    this->component.deinit();
}

// ----------------------------------------------------------------------
// Tests
// ----------------------------------------------------------------------

void ActiveTextLoggerTester ::runNominalTest() {
    printf("Testing writing to console\n");

    // Verifying initial state:
    ASSERT_FALSE(this->component.m_log_file.m_openFile);

    FwEventIdType id = 1;
    Fw::Time timeTag(TimeBase::TB_NONE, 3, 6);
    Fw::LogSeverity severity = Fw::LogSeverity::ACTIVITY_HI;
    Fw::TextLogString text("This component is the greatest!");
    this->invoke_to_TextLogger(0, id, timeTag, severity, text);
    this->component.doDispatch();

    id = 2;
    timeTag.set(TimeBase::TB_PROC_TIME, 4, 7);
    severity = Fw::LogSeverity::ACTIVITY_LO;
    text = "This component is the probably the greatest!";
    this->invoke_to_TextLogger(0, id, timeTag, severity, text);
    this->component.doDispatch();

    // This will output in a different format b/c WORKSTATION_TIME
    id = 3;
    timeTag.set(TimeBase::TB_WORKSTATION_TIME, 5, 876);
    severity = Fw::LogSeverity::WARNING_LO;
    text = "This component is maybe the greatest!";
    this->invoke_to_TextLogger(0, id, timeTag, severity, text);
    this->component.doDispatch();

    // No way to verify internal interface was called, but
    // can manually see the print out to the console

    printf("Testing writing to console and file\n");

    // Setup file for writing to:
    bool stat = this->component.set_log_file("test_file", 512);

    ASSERT_TRUE(stat);
    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    EXPECT_STREQ("test_file", this->component.m_log_file.m_fileName.toChar());
    ASSERT_EQ(0U, this->component.m_log_file.m_currentFileSize);
    ASSERT_EQ(512U, this->component.m_log_file.m_maxFileSize);

    id = 4;
    timeTag.set(TimeBase::TB_NONE, 5, 8);
    severity = Fw::LogSeverity::WARNING_LO;
    const char* severityString = "WARNING_LO";
    text = "This component may be the greatest!";
    this->invoke_to_TextLogger(0, id, timeTag, severity, text);
    this->component.doDispatch();

    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    ASSERT_EQ(strlen(text.toChar()) + 32, this->component.m_log_file.m_currentFileSize);
    ASSERT_EQ(512U, this->component.m_log_file.m_maxFileSize);
    U32 past_size = strlen(text.toChar()) + 32;

    // Read file to verify contents:
    std::ifstream stream1("test_file");
    char oldLine[256];
    while (stream1) {
        char buf[256];
        stream1.getline(buf, 256);
        if (stream1) {
            std::cout << "readLine: " << buf << std::endl;
            char textStr[512];
            snprintf(textStr, sizeof(textStr), "EVENT: (%" PRI_FwEventIdType ") (%d:%d,%d) %s: %s", id,
                     static_cast<FwTimeBaseStoreType>(timeTag.getTimeBase()), timeTag.getSeconds(),
                     timeTag.getUSeconds(), severityString, text.toChar());
            ASSERT_STREQ(textStr, buf);
            (void)Fw::StringUtils::string_copy(oldLine, buf, static_cast<FwSizeType>(sizeof(oldLine)));
        }
    }
    stream1.close();

    id = 5;
    timeTag.set(TimeBase::TB_PROC_TIME, 6, 9);
    severity = Fw::LogSeverity::WARNING_HI;
    severityString = "WARNING_HI";
    text = "This component is probably not the greatest!";
    this->invoke_to_TextLogger(0, id, timeTag, severity, text);
    this->component.doDispatch();

    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    ASSERT_EQ(past_size + strlen(text.toChar()) + 32, this->component.m_log_file.m_currentFileSize);
    ASSERT_EQ(512U, this->component.m_log_file.m_maxFileSize);

    // Test predicted size matches actual:
    FwSizeType fileSize = 0;
    Os::FileSystem::getFileSize("test_file", fileSize);
    ASSERT_EQ(fileSize, this->component.m_log_file.m_currentFileSize);

    // Read file to verify contents:
    std::ifstream stream2("test_file");
    U32 iter = 0;
    while (stream2) {
        char buf[256];
        stream2.getline(buf, 256);
        if (stream2) {
            std::cout << "readLine: " << buf << std::endl;
            // Verify first printed line is still there
            if (iter == 0) {
                ASSERT_EQ(0, strcmp(oldLine, buf));
            }
            // Verify new printed line
            else {
                char textStr[512];
                snprintf(textStr, sizeof(textStr), "EVENT: (%" PRI_FwEventIdType ") (%d:%d,%d) %s: %s", id,
                         static_cast<FwTimeBaseStoreType>(timeTag.getTimeBase()), timeTag.getSeconds(),
                         timeTag.getUSeconds(), severityString, text.toChar());
                ASSERT_EQ(0, strcmp(textStr, buf));
            }
        }
        ++iter;
    }
    stream2.close();

    // Clean up:
    remove("test_file");
}

void ActiveTextLoggerTester ::runOffNominalTest() {
    // TODO file errors- use the Os/Stubs?

    FwSizeType tmp;

    printf("Testing writing text that is larger than FW_INTERNAL_INTERFACE_STRING_MAX_SIZE\n");
    // Can't test this b/c max size of TextLogString is 256 and
    // FW_INTERNAL_INTERFACE_STRING_MAX_SIZE is 512

    printf("Testing writing more than the max file size\n");

    // Setup file for writing to:
    bool stat = this->component.set_log_file("test_file_max", 45);

    ASSERT_TRUE(stat);
    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    ASSERT_STREQ("test_file_max", this->component.m_log_file.m_fileName.toChar());
    ASSERT_EQ(0U, this->component.m_log_file.m_currentFileSize);
    ASSERT_EQ(45U, this->component.m_log_file.m_maxFileSize);
    ASSERT_EQ(Os::FileSystem::OP_OK, Os::FileSystem::getFileSize("test_file_max", tmp));

    // Write once to the file:
    FwEventIdType id = 1;
    Fw::Time timeTag(TimeBase::TB_NONE, 3, 6);
    Fw::LogSeverity severity = Fw::LogSeverity::ACTIVITY_HI;
    const char* severityString = "ACTIVITY_HI";
    Fw::TextLogString text("abcd");
    this->invoke_to_TextLogger(0, id, timeTag, severity, text);
    this->component.doDispatch();

    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    ASSERT_EQ(strlen(text.toChar()) + 33, this->component.m_log_file.m_currentFileSize);
    ASSERT_EQ(45U, this->component.m_log_file.m_maxFileSize);
    U32 past_size = strlen(text.toChar()) + 33;

    // Read file to verify contents:
    std::ifstream stream1("test_file_max");
    char oldLine[256];
    while (stream1) {
        char buf[256];
        stream1.getline(buf, 256);
        if (stream1) {
            std::cout << "readLine: " << buf << std::endl;
            char textStr[512];
            snprintf(textStr, sizeof(textStr), "EVENT: (%" PRI_FwEventIdType ") (%d:%d,%d) %s: %s", id,
                     static_cast<FwTimeBaseStoreType>(timeTag.getTimeBase()), timeTag.getSeconds(),
                     timeTag.getUSeconds(), severityString, text.toChar());
            ASSERT_EQ(0, strcmp(textStr, buf));
            (void)Fw::StringUtils::string_copy(oldLine, buf, static_cast<FwSizeType>(sizeof(oldLine)));
        }
    }
    stream1.close();

    // Write again to the file going over the max:
    text = "This will go over max size of the file!";
    this->invoke_to_TextLogger(0, id, timeTag, severity, text);
    this->component.doDispatch();

    // Verify file was closed and size didn't increase:
    ASSERT_FALSE(this->component.m_log_file.m_openFile);
    ASSERT_EQ(past_size, this->component.m_log_file.m_currentFileSize);
    ASSERT_EQ(45U, this->component.m_log_file.m_maxFileSize);

    // Read file to verify contents didn't change:
    std::ifstream stream2("test_file_max");
    while (stream2) {
        char buf[256];
        stream2.getline(buf, 256);
        if (stream2) {
            std::cout << "readLine: " << buf << std::endl;
            ASSERT_EQ(0, strcmp(oldLine, buf));
        }
    }
    stream2.close();

    printf("Testing with file name that already exists\n");

    // Setup file for writing to that is a duplicate name:
    stat = this->component.set_log_file("test_file_max", 50);

    // Verify made file with 0 suffix:
    ASSERT_TRUE(stat);
    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    ASSERT_STREQ("test_file_max0", this->component.m_log_file.m_fileName.toChar());
    ASSERT_EQ(0U, this->component.m_log_file.m_currentFileSize);
    ASSERT_EQ(50U, this->component.m_log_file.m_maxFileSize);
    ASSERT_EQ(Os::FileSystem::OP_OK, Os::FileSystem::getFileSize("test_file_max0", tmp));

    printf("Testing file name larger than string size\n");

    // Setup filename larger than file name string can accept
    // Maximum valid file name is Fw::FileNameString::STRING_SIZE, add 1 to be too long, and an extra for the \0
    char longFileName[Fw::FileNameString::STRING_SIZE + 2];
    for (U32 i = 0; i < Fw::FileNameString::STRING_SIZE + 1; ++i) {
        longFileName[i] = 'a';
    }
    longFileName[Fw::FileNameString::STRING_SIZE + 1] = 0;
    stat = this->component.set_log_file(longFileName, 50);

    // Verify file not made:
    ASSERT_FALSE(stat);
    ASSERT_FALSE(this->component.m_log_file.m_openFile);
    ASSERT_NE(Os::FileSystem::OP_OK, Os::FileSystem::getFileSize(longFileName, tmp));

    printf("Testing file name of max size and file already exists\n");
    // Maximum valid file name is Fw::FileNameString::STRING_SIZE add one for \0
    char longFileNameDup[Fw::FileNameString::STRING_SIZE + 1];
    for (U32 i = 0; i < Fw::FileNameString::STRING_SIZE; ++i) {
        longFileNameDup[i] = 'a';
    }
    longFileNameDup[Fw::FileNameString::STRING_SIZE] = 0;

    stat = this->component.set_log_file(longFileNameDup, 50);

    // Verify file made successful:
    ASSERT_TRUE(stat);
    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    ASSERT_EQ(Os::FileSystem::OP_OK, Os::FileSystem::getFileSize(longFileNameDup, tmp));

    // Try to make the same one again:
    stat = this->component.set_log_file(longFileNameDup, 50);

    // Verify file not made:
    ASSERT_FALSE(stat);
    ASSERT_FALSE(this->component.m_log_file.m_openFile);
    char longFileNameDupS[81] = "0";
    ASSERT_NE(Os::FileSystem::OP_OK, Os::FileSystem::getFileSize(longFileNameDupS, tmp));

    printf("Testing with file name that already exists and the next 10 suffixes\n");

    // Create 11 files with the same name, and verify 11th fails, and re-uses the original
    const char* baseName = "test_mult_dup";

    // Create first file:
    stat = this->component.set_log_file(baseName, 50);

    ASSERT_TRUE(stat);
    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    ASSERT_EQ(0, strcmp(baseName, this->component.m_log_file.m_fileName.toChar()));
    ASSERT_EQ(Os::FileSystem::OP_OK, Os::FileSystem::getFileSize(baseName, tmp));

    // Create 10 more, which all should succeed:
    char baseNameWithSuffix[128];
    U32 i;
    for (i = 0; i < 10; ++i) {
        stat = this->component.set_log_file(baseName, 50);

        snprintf(baseNameWithSuffix, sizeof(baseNameWithSuffix), "%s%d", baseName, i);
        ASSERT_TRUE(stat);
        ASSERT_TRUE(this->component.m_log_file.m_openFile);
        ASSERT_EQ(0, strcmp(baseNameWithSuffix, this->component.m_log_file.m_fileName.toChar()));
        ASSERT_EQ(Os::FileSystem::OP_OK, Os::FileSystem::getFileSize(baseNameWithSuffix, tmp));
    }

    // Create 11th which will fail and re-use the original:
    stat = this->component.set_log_file(baseName, 50);

    ASSERT_TRUE(stat);
    ASSERT_TRUE(this->component.m_log_file.m_openFile);
    printf("<< %s %s\n", baseName, this->component.m_log_file.m_fileName.toChar());
    ASSERT_EQ(0, strcmp(baseName, this->component.m_log_file.m_fileName.toChar()));
    ASSERT_EQ(Os::FileSystem::OP_OK, Os::FileSystem::getFileSize(baseName, tmp));

    // Clean up:
    remove("test_file_max");
    remove("test_file_max0");
    remove(longFileNameDup);
    remove(baseName);
    for (i = 0; i < 10; ++i) {
        snprintf(baseNameWithSuffix, sizeof(baseNameWithSuffix), "%s%d", baseName, i);
        remove(baseNameWithSuffix);
    }
}

void ActiveTextLoggerTester ::testWorkstationTimestamp() {
    printf("Testing workstation timestamp\n");

    // Setup file for writing to:
    const char* logFileName = "test_file";
    bool stat = this->component.set_log_file(logFileName, 512);
    ASSERT_TRUE(stat);
    ASSERT_TRUE(this->component.m_log_file.m_openFile);

    // Log message:
    FwEventIdType id = 1;
    Fw::Time timeTag(TimeBase::TB_WORKSTATION_TIME, 3, 6);
    Fw::LogSeverity severity = Fw::LogSeverity::ACTIVITY_HI;
    const char* severityString = "ACTIVITY_HI";
    Fw::TextLogString text("This line has a valid timestamp.");
    this->invoke_to_TextLogger(0, id, timeTag, severity, text);
    this->component.doDispatch();

    // Read file to verify contents:
    std::ifstream logStream(logFileName);
    while (logStream) {
        char buf[256];
        logStream.getline(buf, 256);
        if (logStream) {
            std::cout << "readLine: " << buf << std::endl;
            char textStr[512];
            snprintf(textStr, sizeof(textStr), "EVENT: (%" PRI_FwEventIdType ") (%d:%d,%d) %s: %s", id,
                     static_cast<FwTimeBaseStoreType>(timeTag.getTimeBase()), timeTag.getSeconds(),
                     timeTag.getUSeconds(), severityString, text.toChar());
            ASSERT_EQ(0, strcmp(textStr, buf));
        }
    }
    logStream.close();

    // Clean up:
    remove(logFileName);
}

// ----------------------------------------------------------------------
// Helper methods
// ----------------------------------------------------------------------

void ActiveTextLoggerTester ::connectPorts() {
    // TextLogger
    this->connect_to_TextLogger(0, this->component.get_TextLogger_InputPort(0));
}

void ActiveTextLoggerTester ::initComponents() {
    this->init();
    this->component.init(QUEUE_DEPTH, INSTANCE);
}

}  // end namespace Svc
